/* 
 * Linkingsoft fm2018 control
 *
 * Copyright (C) 
 * 2013 - marco <marco@linkingsoft.net>
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 */

#include <linux/types.h>
#include <linux/wakelock.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/mm.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>

#define DEBUG 0
#if DEBUG
	#define INFO(format, arg...) printk("\33[32m [INFO]" format " \33[0m\n", ##arg)
#else
	#define INFO(format, ...) ((void)0)
#endif
#define ERR(format, arg...) printk("\33[31m [ERR]" format " \33[0m\n", ##arg)

#define	FM2018_I2C_ADD		0x60 /* 0xC0 >> 1 */
#define	FM2018_DEV_NAME		"fm2018"

#define FM2018_SYNC_WORD_0	0xFC
#define FM2018_SYNC_WORD_1	0xF3

#define FM2018_MEM_WRITE	0x3B
#define FM2018_MEM_READ		0x37
#define FM2018_REG_WRITE	0x60
#define FM2018_REG_READ		0x6A

#define FM2018_DATA_PORT_HIGH	0x26
#define FM2018_DATA_PORT_LOW	0x25

#define FM2018_I2C_RATE		100000	

static struct i2c_client *this_client = NULL;

/* FM2018 init data */
struct reg_data {
	unsigned int addr;
	unsigned int data;
} fm2018_init[] = {
	{0x1E30,0x0231},
	{0x1E34,0x000A},
	{0x1E3D,0x0C00}, /* mic_volume */
	{0x1E3E,0x0200}, /* spk_volume */
	{0x1E41,0x0101}, /* Num_of_mics */
	{0x1E44,0x0081},
	{0x1E45,0x03CF},
	{0x1E46,0x0010},
	{0x1E47,0x2000},
	{0x1E48,0x1000},
	{0x1E49,0x0880},
	{0x1E4D,0x0180},
	{0x1E52,0x0013},
	{0x1E63,0x0004},
	{0x1E70,0x05C0},
	{0x1E86,0x0009},
	{0x1E87,0x0004},
	{0x1E88,0x3800},
	{0x1E89,0x0001},
	{0x1E8B,0x0180},
	{0x1E8C,0x0080},
	{0x1E92,0x5800},
	{0x1EA0,0x0400},
	{0x1EA1,0x4000},
	{0x1EC1,0x1080},
	{0x1EF8,0x0C00},
	{0x1EF9,0x0300},
	{0x1EFF,0x4800},
	{0x1F00,0x7FFF}, /* mic_cal_diff */
	{0x1F0A,0x0A00},
	{0x1F0C,0x0100},
	{0x1F0D,0x7FFF},
	{0x1E3A,0x0000}, /* Readly flag. D15 must set 0. */
};

static int fm2018_tx_i2c(unsigned int addr, unsigned int val) 
{
	if (!this_client) {
		return -ENODEV;
	}
#define FM2018_TX_MSG_LEN	7
	int res = 0;
	struct i2c_msg msgs;
	unsigned char data[FM2018_TX_MSG_LEN];

	data[0] = FM2018_SYNC_WORD_0;
	data[1] = FM2018_SYNC_WORD_1;
	data[2] = FM2018_MEM_WRITE;
	data[3] = ((addr >> 8) & 0xFF);
	data[4] = (addr & 0xFF);
	data[5] = ((val >> 8) & 0xFF);
	data[6] = (val & 0xFF);

	msgs.addr = FM2018_I2C_ADD;
	msgs.flags = 0;
	msgs.len = FM2018_TX_MSG_LEN;
	msgs.buf = data;
	msgs.scl_rate = FM2018_I2C_RATE;

	res = i2c_transfer(this_client->adapter, &msgs, 1);
	INFO("TX FM2018_MEM_WRITE [0x%04x][0x%04x] res [%d]", addr, val, res);
	if (0 > res) {
		ERR(" I2C TX ERROR [%d]", res);
	}
	return 0;
}

static int fm2018_rx_i2c(unsigned int addr, unsigned int* value) 
{
	if (!this_client) {
		return -ENODEV;
	}
#define FM2018_RX_MSG_LEN	5
	int res = 0;
	struct i2c_msg msgs[2];
	unsigned char data[FM2018_RX_MSG_LEN];
	unsigned char val;

	*value = 0x00;

	data[0] = FM2018_SYNC_WORD_0;
	data[1] = FM2018_SYNC_WORD_1;
	data[2] = FM2018_MEM_READ;
	data[3] = ((addr >> 8) & 0xff);
	data[4] = (addr & 0xff);

	msgs[0].len = 5;
	msgs[0].addr = FM2018_I2C_ADD;
	msgs[0].buf = data;
	msgs[0].scl_rate = FM2018_I2C_RATE;
	INFO(" Send FM2018_MEM_READ ");
	res = i2c_transfer(this_client->adapter, msgs, 1);
	INFO(" Send FM2018_MEM_READ res [%d] ", res);
	if (0 > res){
		ERR( "I2C RX FM2018_MEM_READ ERROR [%d]", res);
		return -1;
	}

	data[0] = FM2018_SYNC_WORD_0;
	data[1] = FM2018_SYNC_WORD_1;
	data[2] = FM2018_REG_WRITE;
	data[3] = FM2018_DATA_PORT_HIGH;
	msgs[0].len = 4;
	msgs[0].addr = FM2018_I2C_ADD;
	msgs[0].flags = 0;
	msgs[0].scl_rate = FM2018_I2C_RATE;
	msgs[0].buf = data;

	msgs[1].len = 1;
	msgs[1].addr = FM2018_I2C_ADD;
	msgs[1].flags = I2C_M_RD;
	msgs[1].scl_rate = FM2018_I2C_RATE;
	msgs[1].buf = &val;

	INFO(" Send FM2018_DATA_PORT_HIGH ");
	res = i2c_transfer(this_client->adapter, msgs, 2);
	INFO(" Send FM2018_DATA_PORT_HIGH res [%d] ", res);
	if (0 > res){
		ERR( "I2C RX READ FM2018_DATA_PORT_HIGH ERROR [%d]", res);
		return -1;
	}
	*value = (val << 8) & 0xFF00;
	INFO(" FM2018_DATA_PORT_HIGH value [0x%04x] ", *value);

	data[0] = FM2018_SYNC_WORD_0;
	data[1] = FM2018_SYNC_WORD_1;
	data[2] = FM2018_REG_WRITE;
	data[3] = FM2018_DATA_PORT_LOW;
	msgs[0].len = 4;
	msgs[0].addr = FM2018_I2C_ADD;
	msgs[0].flags = 0;
	msgs[0].buf = data;
	msgs[0].scl_rate = FM2018_I2C_RATE;

	msgs[1].len = 1;
	msgs[1].addr = FM2018_I2C_ADD;
	msgs[1].scl_rate = FM2018_I2C_RATE;
	msgs[1].buf = &val;

	INFO(" Send FM2018_DATA_PORT_LOW ");
	res = i2c_transfer(this_client->adapter, msgs, 2);
	INFO(" Send FM2018_DATA_PORT_LOW res [%d] ", res);
	if (0 > res) {
		ERR( "I2C RX READ FM2018_DATA_PORT_LOW ERROR [%d]", res);
		return -1;
	}
	*value += val & 0xFF;
	INFO(" FM2018_DATA_PORT_LOW value [0x%04x] ", *value);
	return 0;
}

static int fm2018_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	INFO("--- fm2018_probe ---");

	unsigned char data[16] = {0};
	int res = 0;

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) 
	{
		ERR("functionality check failed");
		res = -ENODEV;
	}

	INFO("client [%p]", client);

	this_client = client;

/* init fm2018 */
	int i = 0;
#define ARRAYSIZE(a) sizeof(a)/sizeof(a[0])
	for (i = 0; i < ARRAYSIZE(fm2018_init); i++) {
		fm2018_tx_i2c(fm2018_init[i].addr, fm2018_init[i].data);
	}
/* test rx
	if (this_client) {
		unsigned int addr = 0x1E3D;
		unsigned int data = 0x0300;
		fm2018_rx_i2c(addr, &data);
	}
*/
	return res;
}

static int fm2018_remove(struct i2c_client *client)
{
	INFO("--- fm2018_remove ---");
	return 0;
}

static const struct i2c_device_id fm2018_id[] = {
	{ FM2018_DEV_NAME, 0 },
	{ }
};

static struct i2c_driver fm2018_driver = {
	.probe 		= fm2018_probe,
	.remove 	= fm2018_remove,
	.id_table	= fm2018_id,
	.driver 	= {
		.owner	= THIS_MODULE,
		.name	= FM2018_DEV_NAME,
	},
};


static int __init fm2018_init_module(void)
{
	INFO(" ---FM2018 INIT--- ");
	return i2c_add_driver(&fm2018_driver);
}

static __exit void fm2018_remove_module(void)
{
	INFO(" ---FM2018 EXIT--- ");
	i2c_del_driver(&fm2018_driver);
    return;
}

/****************************************/
#if 1 //DEBUG
module_init(fm2018_init_module);
module_exit(fm2018_remove_module);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Linkingsoft fm2018 control module");
MODULE_AUTHOR("marco <marco@linkingsoft.net>");
#else
subsys_initcall(fm2018_init_module);
#endif
